#include <string.h>
#include <stdlib.h>

#include "macros.h"
#include "opcodes.h"
#include "compiler.h"
#include "lpc.h"
#include "hash.h"
#include "grammar.tab.h"
#include "ident.h"

/* identifier hash table stuff, size must be an even power of two */
#define IDENT_HASH_SIZE 1024
#define IDENT_HASH(s) (whashstr((s), 20) & (IDENT_HASH_SIZE - 1))

#define NELEM(a) (sizeof (a) / sizeof((a)[0]))

/*
 * The number of arguments stated below, are used by the compiler.
 * If min == max, then no information has to be coded about the
 * actual number of arguments. Otherwise, the actual number of arguments
 * will be stored in the byte after the instruction.
 * A maximum value of -1 means unlimited maximum value.
 *
 * If an argument has type 0 (T_INVALID) specified, then no checks will
 * be done at run time.
 *
 * The argument types are currently not checked by the compiler,
 * only by the runtime.
 */
keyword_t predefs[] =
#include "efun_defs.c"

static keyword_t reswords[] =
{
	{"break", L_BREAK, 0},
	{"continue", L_CONTINUE, 0},
	{"do", L_DO, 0},
	{"else", L_ELSE, 0},
	{"float", L_BASIC_TYPE, TYPE_REAL},
	{"for", L_FOR, 0},
	{"foreach", L_FOREACH, 0},
	{"if", L_IF, 0},
	{"in", L_IN, 0},
	{"int", L_BASIC_TYPE, TYPE_NUMBER},
	{"mapping", L_BASIC_TYPE, TYPE_MAPPING},
	{"mixed", L_BASIC_TYPE, TYPE_ANY},
	{"object", L_BASIC_TYPE, TYPE_OBJECT},
	{"return", L_RETURN, 0},
	{"string", L_BASIC_TYPE, TYPE_STRING},
	{"void", L_BASIC_TYPE, TYPE_VOID},
	{"while", L_WHILE, 0},
};

int arrow_efun, evaluate_efun, this_efun, to_float_efun, to_int_efun, new_efun;

static ident_hash_elem_t **ident_hash_table;
static ident_hash_elem_t **ident_hash_head;
static ident_hash_elem_t **ident_hash_tail;

static ident_hash_elem_t *ident_dirty_list = 0;

instr_t instrs[MAX_INSTRS];

/* The identifier table is hashed for speed.  The hash chains are circular
 * linked lists, so that we can rotate them, since identifier lookup is
 * rather irregular (i.e. we're likely to be asked about the same one
 * quite a number of times in a row).  This isn't as fast as moving entries
 * to the front but is done this way for two reasons:
 *
 * 1. this allows us to keep permanent identifiers consecutive and clean
 *    up faster
 * 2. it would only be faster in cases where two identifiers with the same
 *    hash value are used often within close proximity in the source.
 *    This should be rare, esp since the hash table is fairly sparse.
 *
 * ident_hash_table[hash] points to our current position (last lookup)
 * ident_hash_head[hash] points to the first permanent identifier
 * ident_hash_tail[hash] points to the last one
 * ident_dirty_list is a linked list of identifiers that need to be cleaned
 * when we're done; this happens if you define a global or function with
 * the same name as an efun or sefun.
 */

ident_hash_elem_t *lookup_ident(char *name) {
	int h = IDENT_HASH(name);
	ident_hash_elem_t *hptr, *hptr2;

	if ((hptr = ident_hash_table[h])) {
		if (!strcmp(hptr->name, name)) {
			if ((hptr->token & IHE_RESWORD) || (hptr->sem_value)) {
				return hptr;
			} else {
				return 0;
			}
		}
		hptr2 = hptr->next;
		while (hptr2 != hptr) {
			if (!strcmp(hptr2->name, name)) {
				if ((hptr2->token & IHE_RESWORD) || (hptr2->sem_value)) {
					ident_hash_table[h] = hptr2;
					return hptr2;
				} else {
					return 0;
				}
			}
			hptr2 = hptr2->next;
		}
	}
	return 0;
}

/* a linked chain of memory to speed up allocate local names */
typedef struct lname_linked_buf_s {
	struct lname_linked_buf_s *next;
	char block[4096];
} lname_linked_buf_t;

static lname_linked_buf_t *lnamebuf = 0;

static int lb_index = 4096;

static char *alloc_local_name(char *name) {
	int len = strlen(name) + 1;
	char *res;

	if (lb_index + len > 4096) {
		lname_linked_buf_t *new_buf;
		new_buf = ALLOCATE(lname_linked_buf_t);
		new_buf->next = lnamebuf;
		lnamebuf = new_buf;
		lb_index = 0;
	}
	res = &(lnamebuf->block[lb_index]);
	strcpy(res, name);
	lb_index += len;
	return res;
}

/* a linked chain of memory to speed up allocate ident_hash_elem_t's */
typedef struct ident_hash_elem_list_s {
	struct ident_hash_elem_list_s *next;
	ident_hash_elem_t items[128];
} ident_hash_elem_list_t;

static ident_hash_elem_list_t *ihe_list = 0;

static int num_free = 0;

void free_unused_identifiers() {
	ident_hash_elem_list_t *ihel, *next;
	lname_linked_buf_t *lnb, *lnbn;
	int i;

	/* clean up dirty idents */
	while (ident_dirty_list) {
		if (ident_dirty_list->dn.function_num != -1) {
			ident_dirty_list->dn.function_num = -1;
			ident_dirty_list->sem_value--;
		}
		if (ident_dirty_list->dn.global_num != -1) {
			ident_dirty_list->dn.global_num = -1;
			ident_dirty_list->sem_value--;
		}
		ident_dirty_list = ident_dirty_list->next_dirty;
	}

	for (i = 0; i < IDENT_HASH_SIZE; i++)
		if ((ident_hash_table[i] = ident_hash_head[i]))
			ident_hash_tail[i]->next = ident_hash_head[i];

	ihel = ihe_list;
	while (ihel) {
		next = ihel->next;
		free(ihel);
		ihel = next;
	}
	ihe_list = 0;
	num_free = 0;

	lnb = lnamebuf;
	while (lnb) {
		lnbn = lnb->next;
		free(lnb);
		lnb = lnbn;
	}
	lnamebuf = 0;
	lb_index = 4096;
}

static ident_hash_elem_t *quick_alloc_ident_entry() {
	if (num_free) {
		num_free--;
		return &(ihe_list->items[num_free]);
	} else {
		ident_hash_elem_list_t *ihel;
		ihel = ALLOCATE(ident_hash_elem_list_t);
		ihel->next = ihe_list;
		ihe_list = ihel;
		num_free = 127;
		return &(ihe_list->items[127]);
	}
}

ident_hash_elem_t *find_or_add_ident(char *name, int flags) {
	int h = IDENT_HASH(name);
	ident_hash_elem_t *hptr, *hptr2;

	if ((hptr = ident_hash_table[h])) {
		if (!strcmp(hptr->name, name)) {
			if ((hptr->token & IHE_PERMANENT) && (flags & FOA_GLOBAL_SCOPE)
					&& (hptr->dn.function_num==-1)&&(hptr->dn.global_num==-1)) {
				hptr->next_dirty = ident_dirty_list;
				ident_dirty_list = hptr;
			}
			return hptr;
		}
		hptr2 = hptr->next;
		while (hptr2 != hptr) {
			if (!strcmp(hptr2->name, name)) {
				if ((hptr2->token & IHE_PERMANENT)&&(flags & FOA_GLOBAL_SCOPE)
						&& (hptr2->dn.function_num==-1)&&(hptr2->dn.global_num==-1)) {
					hptr2->next_dirty = ident_dirty_list;
					ident_dirty_list = hptr2;
				}
				ident_hash_table[h] = hptr2; /* rotate */
				return hptr2;
			}
			hptr2 = hptr2->next;
		}
	}

	hptr = quick_alloc_ident_entry();
	if (!(hptr2 = ident_hash_tail[h]) && !(hptr2 = ident_hash_table[h])) {
		ident_hash_table[h] = hptr->next = hptr;
	} else {
		hptr->next = hptr2->next;
		hptr2->next = hptr;
	}

	if (flags & FOA_NEEDS_MALLOC) {
		hptr->name = alloc_local_name(name);
	} else {
		hptr->name = name;
	}
	hptr->token = 0;
	hptr->sem_value = 0;
	hptr->dn.simul_num = -1;
	hptr->dn.local_num = -1;
	hptr->dn.global_num = -1;
	hptr->dn.efun_num = -1;
	hptr->dn.function_num = -1;
	return hptr;
}

ident_hash_elem_t *find_or_add_perm_ident(char *name) {
	int h = IDENT_HASH(name);
	ident_hash_elem_t *hptr, *hptr2;

	if ((hptr = ident_hash_table[h])) {
		if (!strcmp(hptr->name, name)) return hptr;
		hptr2 = hptr->next;
		while (hptr2 != hptr) {
			if (!strcmp(hptr2->name, name)) return hptr2;
			hptr2 = hptr2->next;
		}
		hptr = ALLOCATE(ident_hash_elem_t);
		hptr->next = ident_hash_head[h]->next;
		ident_hash_head[h]->next = hptr;
		if (ident_hash_head[h] == ident_hash_tail[h])
			ident_hash_tail[h] = hptr;
	} else {
		hptr = (ident_hash_table[h] = ALLOCATE(ident_hash_elem_t));
		ident_hash_head[h] = hptr;
		ident_hash_tail[h] = hptr;
		hptr->next = hptr;
	}
	hptr->name = name;
	hptr->token = 0;
	hptr->sem_value = 0;
	hptr->dn.simul_num = -1;
	hptr->dn.local_num = -1;
	hptr->dn.global_num = -1;
	hptr->dn.efun_num = -1;
	hptr->dn.function_num = -1;
	return hptr;
}

static void add_keyword_t(char *name, keyword_t *entry) {
	int h = IDENT_HASH(name);

	if (ident_hash_table[h]) {
		entry->next = ident_hash_head[h]->next;
		ident_hash_head[h]->next = (ident_hash_elem_t *)entry;
		if (ident_hash_head[h] == ident_hash_tail[h])
			ident_hash_tail[h] = (ident_hash_elem_t *)entry;
	} else {
		ident_hash_head[h] = (ident_hash_elem_t *)entry;
		ident_hash_tail[h] = (ident_hash_elem_t *)entry;
		ident_hash_table[h] = (ident_hash_elem_t *)entry;
		entry->next = (ident_hash_elem_t *)entry;
	}
	entry->token |= IHE_RESWORD;
}

#define add_instr_name(w, x, y, z) int_add_instr_name(w, y, z)

static void int_add_instr_name(char *name, int n, short t)
{
	instrs[n].name = name;
	instrs[n].ret_type = t;
}

static void init_instrs()
{
	int i, n;

	for (i = 0; i < BASE; i++) {
		instrs[i].ret_type = -1;
	}
	for (i = 0; i < NELEM(predefs); i++) {
		n = predefs[i].token;
		instrs[n].min_arg = predefs[i].min_args;
		instrs[n].max_arg = predefs[i].max_args;
		instrs[n].name = predefs[i].word;
		instrs[n].type[0] = predefs[i].arg_type1;
		instrs[n].type[1] = predefs[i].arg_type2;
		instrs[n].type[2] = predefs[i].arg_type3;
		instrs[n].type[3] = predefs[i].arg_type4;
		instrs[n].Default = predefs[i].Default;
		instrs[n].ret_type = predefs[i].ret_type;
		instrs[n].arg_index = predefs[i].arg_index;
	}

	/*
	 * eoperators have a return type now.  T_* is used instead of TYPE_*
	 * since operators can return multiple types.
	 */
	add_instr_name("pop", "pop_stack();\n", F_POP_VALUE, -1);
	add_instr_name("number", 0, F_NUMBER, T_NUMBER);
	add_instr_name("real", 0, F_REAL, T_REAL);
	add_instr_name("byte", 0, F_BYTE, T_NUMBER);
	add_instr_name("-byte", 0, F_NBYTE, T_NUMBER);
	add_instr_name("string", 0, F_STRING, T_STRING);
	add_instr_name("short_string", 0, F_SHORT_STRING, T_STRING);
	add_instr_name("const0", "push_number(0);\n", F_CONST0, T_NUMBER);
	add_instr_name("const1", "push_number(1);\n", F_CONST1, T_NUMBER);
	add_instr_name("aggregate", "C_AGGREGATE(%i);\n", F_AGGREGATE, T_ARRAY);
	add_instr_name("aggregate_assoc", "C_AGGREGATE_ASSOC(%i);\n", F_AGGREGATE_ASSOC, T_MAPPING);
#ifdef DEBUG
	add_instr_name("break_point", "break_point();\n", F_BREAK_POINT, -1);
#endif
	add_instr_name("branch_when_zero", 0, F_BRANCH_WHEN_ZERO, -1);
	add_instr_name("branch_when_non_zero", 0, F_BRANCH_WHEN_NON_ZERO, -1);
	add_instr_name("branch", 0, F_BRANCH, -1);
	add_instr_name("bbranch_when_zero", 0, F_BBRANCH_WHEN_ZERO, -1);
	add_instr_name("bbranch_when_non_zero", 0, F_BBRANCH_WHEN_NON_ZERO, -1);
	add_instr_name("bbranch", 0, F_BBRANCH, -1);
	add_instr_name("branch_ne", 0, F_BRANCH_NE, -1);
	add_instr_name("branch_ge", 0, F_BRANCH_GE, -1);
	add_instr_name("branch_le", 0, F_BRANCH_LE, -1);
	add_instr_name("branch_eq", 0, F_BRANCH_EQ, -1);
	add_instr_name("bbranch_lt", 0, F_BBRANCH_LT, -1);
	add_instr_name("foreach", 0, F_FOREACH, -1);
	add_instr_name("next_foreach", "c_next_foreach();\n", F_NEXT_FOREACH, -1);
	add_instr_name("exit_foreach", "c_exit_foreach();\n", F_EXIT_FOREACH, -1);
	add_instr_name("loop_cond_local", "C_LOOP_COND_LV(%i, %i); if (lpc_int)\n", F_LOOP_COND_LOCAL, -1);
	add_instr_name("loop_cond_number", "C_LOOP_COND_NUM(%i, %i); if (lpc_int)\n", F_LOOP_COND_NUMBER, -1);
	add_instr_name("||", 0, F_LOR, -1);
	add_instr_name("&&", 0, F_LAND, -1);
	add_instr_name("call", "c_call(%i, %i);\n", F_CALL_FUNCTION_BY_ADDRESS, T_ANY);
	add_instr_name("call_inherited", "c_call_inherited(%i, %i, %i);\n", F_CALL_INHERITED, T_ANY);
	add_instr_name("return", "c_return();\nreturn;\n", F_RETURN, -1);
	add_instr_name("return_zero", "c_return_zero();\nreturn;\n", F_RETURN_ZERO, -1);
	add_instr_name("==", "f_eq();\n", F_EQ, T_NUMBER);
	add_instr_name("!=", "f_ne();\n", F_NE, T_NUMBER);
	add_instr_name("<=", "c_le();\n", F_LE, T_NUMBER);
	add_instr_name("<", "c_lt();\n", F_LT, T_NUMBER);
	add_instr_name(">=", "c_ge();\n", F_GE, T_NUMBER);
	add_instr_name(">", "c_gt();\n", F_GT, T_NUMBER);
	add_instr_name("transfer_local", "C_TRANSFER_LOCAL(%i);\n", F_TRANSFER_LOCAL, T_ANY);
	add_instr_name("local", "C_LOCAL(%i);\n", F_LOCAL, T_ANY);
	add_instr_name("local_lvalue", "C_LVALUE(fp + %i);\n", F_LOCAL_LVALUE, T_LVALUE);
	add_instr_name("global", "C_GLOBAL(%i);\n", F_GLOBAL, T_ANY);
	add_instr_name("global_lvalue", "C_LVALUE(&current_object->variables[variable_index_offset + %i]);\n", F_GLOBAL_LVALUE, T_LVALUE);
	add_instr_name("index", "c_index();\n", F_INDEX, T_ANY);
	add_instr_name("index_lvalue", "push_indexed_lvalue(0);\n", F_INDEX_LVALUE, T_LVALUE|T_LVALUE_BYTE);
	add_instr_name("assign", "c_assign();\n", F_ASSIGN, T_ANY);
	add_instr_name("(void)assign", "c_void_assign();\n", F_VOID_ASSIGN, T_NUMBER);
	add_instr_name("(void)assign_local", "c_void_assign_local(fp + %i);\n", F_VOID_ASSIGN_LOCAL, T_NUMBER);
	add_instr_name("+", "c_add();\n", F_ADD, T_ANY);
	add_instr_name("subtract", "c_subtract();\n", F_SUBTRACT, T_NUMBER | T_REAL | T_ARRAY);
	add_instr_name("*", "c_multiply();\n", F_MULTIPLY, T_REAL | T_NUMBER | T_MAPPING);
	add_instr_name("/", "c_divide();\n", F_DIVIDE, T_REAL | T_NUMBER);
	add_instr_name("%", "c_mod();\n", F_MOD, T_NUMBER);
	add_instr_name("!", "c_not();\n", F_NOT, T_NUMBER);
	add_instr_name("-", "c_negate();\n", F_NEGATE, T_NUMBER | T_REAL);
	add_instr_name("~", "c_compl();\n", F_COMPL, T_NUMBER);
	add_instr_name("simul_efun", "call_simul_efun(%i, (lpc_int = %i + num_varargs, num_varargs = 0, lpc_int));\n", F_SIMUL_EFUN, T_ANY);
	add_instr_name("expand_varargs", 0, F_EXPAND_VARARGS, -1);
}

void init_identifiers() {
	int i;
	ident_hash_elem_t *ihe;

	init_instrs();

	/* allocate all three tables together */
	ident_hash_table = CALLOCATE(IDENT_HASH_SIZE * 3, ident_hash_elem_t *);
	ident_hash_head = (ident_hash_elem_t **)&ident_hash_table[IDENT_HASH_SIZE];
	ident_hash_tail = (ident_hash_elem_t **)&ident_hash_table[2*IDENT_HASH_SIZE];

	/* clean all three tables */
	for (i=0; i<IDENT_HASH_SIZE * 3; i++) {
		ident_hash_table[i]=0;
	}

	/* add the reserved words */
	for (i=0; i<NELEM(reswords); i++) {
		add_keyword_t(reswords[i].word, &reswords[i]);
	}

	/* add the efuns */
	for (i=0; i<NELEM(predefs); i++) {
		if (strcmp(predefs[i].word, "call_other") == 0)
			arrow_efun = i;
		else if (strcmp(predefs[i].word, "evaluate") == 0)
			evaluate_efun = i;
		else if (strcmp(predefs[i].word, "this_object") == 0)
			this_efun = i;
		else if (strcmp(predefs[i].word, "to_int") == 0)
			to_int_efun = i;
		else if (strcmp(predefs[i].word, "to_float") == 0)
			to_float_efun = i;
		else if (strcmp(predefs[i].word, "new") == 0)
			new_efun = i;

		ihe = find_or_add_perm_ident(predefs[i].word);
		ihe->token |= IHE_EFUN;
		ihe->sem_value++;
		ihe->dn.efun_num = i;
	}
}
